-------------------------------------------------------------------------------
--	 File name: sakuyaTimeStop.lua
--		Author: Sploder
-- 		  Date: 03/12/2010
-- Description: This file contains the code for Sakuya's time stopping ability.
--
-------------------------------------------------------------------------------
sakuyaTimeStop = {}

sakuyaTimeStop.On			= false		-- This keeps track of whether or not time has been stopped.

sakuyaTimeStop.Velocities	= {}		-- This is a table with the velocities of all the entities before time was stopped.
sakuyaTimeStop.EnableMove	= {}		-- This is a table of the frozen statuses of all the entities before time was stopped.
sakuyaTimeStop.Timers		= {}		-- This is a table with the timers of all the entities before time was stopped.
sakuyaTimeStop.Timers2		= {}		-- This is a table with the second timers of all the entities before time was stopped.
sakuyaTimeStop.AIState		= 0			-- This will store whether AI was enabled or not before time was stopped.

-------------------------------------------------------------------------------
--	  Function: local ReturnFalse( player )
-- Description: This function is used in hooks to disable various features,
--				except for the player who stopped time.
--		 Input: player - The player entity who stopped time.
-------------------------------------------------------------------------------
local function ReturnFalse( ply )
	-- If the player isn't the one who stopped time, disable.
	if (ply != sakuyaTimeStop.Activator)
	then
		return false
	end
	-- Otherwise, don't disable.
	return true
end

-------------------------------------------------------------------------------
--	  Function: sakuyaTimeStop.AddEntity( entity )
-- Description: This function freezes an entity, as well as adding its
--				information to the timestop tables.
--		 Input: v - The entity to freeze!
-------------------------------------------------------------------------------
function sakuyaTimeStop.AddEntity( v )
	local class = v:GetClass()
		
	-- If the entity is a danmaku projectile, record its timer, then set it's timer to a large number.
	if (class == "sent_proj_sakuyaknife01" || class == "sent_proj_sakuyaknife02" || class == "sent_proj_cirnoice01" || class == "sent_proj_cirnoice02" || class == "sent_proj_cirnoice03")
	then
		sakuyaTimeStop.Timers[v] = v.timer - CurTime()
		v.timer = CurTime() + 9999
		
		-- If the entity is specifically a special "Killing Doll" knife, set it's angular speed, too.
		if (class == "sent_proj_sakuyaknife02")
		then
			v.angleSpeed = 0
		end
		
		-- If the entity is specifically one of the shots with a second timer, record that second timer.
		if (class == "sent_proj_cirnoice01" || class == "sent_proj_cirnoice03")
		then
			sakuyaTimeStop.Timers2[v] = v.timer2 - CurTime()
			v.timer = CurTime() + 9999
		end
	end
	
	-- If we can get the physics object, record the object's information, then freeze it.
	if (v.GetPhysicsObject)
	then
		local phys = v:GetPhysicsObject()
		if (phys:IsValid())
		then
			sakuyaTimeStop.Velocities[v]	= phys:GetVelocity()
			sakuyaTimeStop.EnableMove[v]	= phys:IsMoveable()
			phys:EnableMotion(false)
			phys:EnableCollisions(true)
		end
	end
end

-------------------------------------------------------------------------------
--	  Function: sakuyaTimeStop.Start( ply )
-- Description: This function is called when we want to stop time.
--		 Input: ply - The player entity who called the function.
-------------------------------------------------------------------------------
function sakuyaTimeStop.Start( ply )
	-- If the player who called the function doesn't have the weapon, or time is already stopped, just return early.
	if (!ply:HasWeapon("weapon_sakuya") || sakuyaTimeStop.On)
	then
		return
	end
	
	-- Remember the player who called this function.
	sakuyaTimeStop.Activator = ply
	-- Turn it on.
	sakuyaTimeStop.On = true
	
	-- Loop through the list of players, freezing all of them except the caller.
	for k, v in pairs(player.GetAll())
	do
		-- Visual effects go here.
		
		v:ConCommand("pp_colormod 1")--Yes they can disable it by doing pp_colormod 0, but it's not an important effect, not worth making a clientside hook!
		v:ConCommand("pp_colormod_brightness 0")
		v:ConCommand("pp_colormod_color 0")
		v:ConCommand("pp_colormod_contrast 1")
		v:ConCommand("pp_colormod_addr 0")
		v:ConCommand("pp_colormod_addg 0")
		v:ConCommand("pp_colormod_addb 0")
		v:ConCommand("pp_colormod_mulr 0")
		v:ConCommand("pp_colormod_mulg 0")
		v:ConCommand("pp_colormod_mulb 0")
		
		v:ChatPrint(ply:Nick().." has stopped time!")
		
		-- If this player isn't the caller, freeze him!
		if (v != ply)
		then
			v:Lock()
		end
	end
	
	-- Loop through all the entities and freeze them in place.
	for k, v in pairs(ents.GetAll())
	do
		-- I moved all this into that function up there.
		sakuyaTimeStop.AddEntity(v)
	end
	
	-- Disable AI.
	sakuyaTimeStop.AIState = GetConVarNumber("ai_disabled")
	game.ConsoleCommand("ai_disabled 1\n")
	
	-- Add some hooks to cover some other events. -------------
	hook.Add("PlayerUse", "SakuyaTimeStop", ReturnFalse)		-- Disable the player's use key.
	hook.Add("CanPlayerSuicide", "SakuyaTimeStop", ReturnFalse)	-- Disable the player's ability to kill himself...
	
	-- Disable sandbox-related stuff.
	hook.Add("PlayerSpawnProp", "SakuyaTimeStop", ReturnFalse)
	hook.Add("PlayerSpawnSENT", "SakuyaTimeStop", ReturnFalse)
	hook.Add("PlayerSpawnNPC",	"SakuyaTimeStop", ReturnFalse)
	hook.Add("CanPlayerEnterVehicle", "SakuyaTimeStop", ReturnFalse)
	
	-- If the player who froze time spawns anything, be sure freeze them and add them to the list.
	hook.Add("PlayerSpawnedProp", "SakuyaTimeStop",
		function (x,y, entity)
			AddEntity(entity)
		end
	)
	hook.Add("PlayerSpawnedSENT", "SakuyaTimeStop",
		function (x, entity)
			AddEntity(entity)
		end
	)
	
	-- Handle player disconnects.
	hook.Add("PlayerDisconnected", "SakuyaTimeStop",
		function( ply )
			-- Remove the disconnected player from the frozen players list.
			
			-- If the disconnected player was the one who stopped time, start time again.
			if (ply == sakuyaTimeStop.Activator)
			then
				sakuyaTimeStop.Stop(ply)
			end
		end
	)
	
	-- Handle player spawns.
	hook.Add("PlayerSpawn", "SakuyaTimeStop",
		function( ply )
			-- Lock the player!
			ply:Lock(true)
		end
	)
	
	-- Handle the death of the player who stopped time.
	hook.Add("DoPlayerDeath", "SakuyaTimeStop",
		function ( ply )
			-- If the player who died was the one who stopped time, start time again.
			if (ply == sakuyaTimeStop.Activator)
			then
				sakuyaTimeStop.Stop(ply)
			end
		end
	)
end

-------------------------------------------------------------------------------
--	  Function: sakuyaTimeStop.Stop( ply )
-- Description: This function is called when we want to start time again.
--		 Input: ply - The player entity that called this function.
-------------------------------------------------------------------------------
function sakuyaTimeStop.Stop( ply )
	-- If the player isn't an admin and isn't the activator, or if time isn't even stopped, return early.
	if ((!ply:IsAdmin() && (ply != sakuyaTimeStop.Activator)) || !sakuyaTimeStop.On)
	then
		return
	end
	
	-- Loop through all the players and unlock all of them.
	for k, v in pairs(player.GetAll())
	do
	
		v:ConCommand("pp_colormod 0")
	
		v:ChatPrint("Time restored to normal!")
	
		-- If the player isn't the activator, unlock him.
		if (v != sakuyaTimeStop.Activator)
		then
			v:UnLock()
		end
	end
		
	-- Loop through all the entities and send them back on their way.
	for k, v in pairs(ents.GetAll())
	do
		local move	= sakuyaTimeStop.EnableMove[v]
		local vel	= sakuyaTimeStop.Velocities[v]
		local class	= v:GetClass()
		
		-- If we can get the physics object and it had motion and velocity, make its motion the same as it was before.
		if (v.GetPhysicsObject && move && vel)
		then
			local phys = v:GetPhysicsObject()
			-- If the physics object is valid, set motion and velocity.
			if (phys:IsValid())
			then
				phys:EnableMotion(move)
				phys:EnableCollisions(true)
				phys:SetVelocity(vel)
			-- Otherwise, just set local velocity.
			else
				v:SetLocalVelocity(vel)
			end
		-- If all we have is the physics object, then just reenable collisions.
		elseif (v.GetPhysicsObject)
		then
			local phys = v:GetPhysicsObject()
			if (phys:IsValid())
			then
				phys:EnableCollisions(true)
			end
		end
		
		-- Special stuff for danmaku shots. -------------------
		-- If the entity is one of sakuya's knives, reset its timer to what it was before.
		if (class == "sent_proj_sakuyaknife01" || class == "sent_proj_sakuyaknife02" || class == "sent_proj_cirnoice01" || class == "sent_proj_cirnoice02" || class == "sent_proj_cirnoice03")
		then
			v.timer = sakuyaTimeStop.Timers[v] + CurTime()
			
			-- If the entity is specifically a special "Killing Doll" knife, reset its angular speed, too.
			if (class == "sent_proj_sakuyaknife02")
			then
				v.angleSpeed = v.normalAngSpeed
			end
			
			-- If the entity is specifically one of the shots with a second timer, record that second timer.
			if (class == "sent_proj_cirnoice01" || class == "sent_proj_cirnoice03")
			then
				v.timer2 = SakuyaTimeStop.Timers2[v] + CurTime()
			end
		end
	end
	
	-- Clear the tables.
	sakuyaTimeStop.Velocities	= {}
	sakuyaTimeStop.EnableMove	= {}
	sakuyaTimeStop.Timers		= {}
	
	-- Set AI back to what it was before.
	game.ConsoleCommand("ai_disabled " .. sakuyaTimeStop.AIState .. "\n")
	
	-- Remove all those hooks.
	for k, v in pairs(hook.GetTable())
	do
		for a, b in pairs(v)
		do
			if (a == "SakuyaTimeStop")
			then
				hook.Remove(k, a)
			end
		end
	end
	
	-- Forget the activator.
	sakuyaTimeStop.Activator = nil
	-- Turn it off.
	sakuyaTimeStop.On = false
end
-- Add a console command for starting time again (so admins can start it up again if there are problems)
concommand.Add("SakuyaStartTime", sakuyaTimeStop.Stop)